package us.inc.sccn.main;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.bluetooth.DiscoveryListener;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;


import lsl_orig.*;

public class Receiver extends Thread implements DiscoveryListener {

	protected UUID uuid = new UUID(0x1101); // serial port profile
	private boolean Recording = false;
	private boolean stopBT = false;
	private String bluetoothAddress= "";
	private int numChans = 4;
	private double samplingRate = 128;
	private RemoteDevice[] btDevices = new RemoteDevice[10];
	private int curBtDeviceIdx = 0;

	// LSL variables
	private  liblsl lsl, lsl2;
	private  stream_info sinfo, sinfo2;
	private  stream_outlet soutlet, soutlet2;
	private  vectorf sample, sample2;
	//private  String rootpath = "/Users/timmullen/Documents/WORK/Toolboxes/labstreaminglayer/LSL/liblsl-Java";
	private  final double lsl_timeout = 0; // timeout (in sec)
	private boolean SKIP_BT=false;
	public void skipBT(boolean tag){
		SKIP_BT = tag;
	}
	public void run(){
		System.out.println("Loading LSL library...");
		//System.out.println(System.getProperty("user.dir"));
		System.load(System.getProperty("user.dir") + "/MacMindoReceiver_lib/liblslJNI32.jnilib");
		if(SKIP_BT)
			getFakeBTDevices();
		else
			getBTDevice();
		//
		while(true);
	}
	/**
	 * This method is to generate a sin wave and push it to LSL, so we don't need physical devices existed.
	 */
	private void getFakeBTDevices(){
		//select how many fake devices you need and connect to them!
		curBtDeviceIdx =2;
		showMsg("--------------------------");
		showMsg("Select a device [0]-[" + (curBtDeviceIdx-1) + "]");
		for (int k=0;k<curBtDeviceIdx;k++) {
			showMsg("["+k + "] Virtual device "+k);
		}
		showMsg("["+curBtDeviceIdx + "] : connect to both virtual devices");
		System.out.print("> ");
		// ...device selection
		Scanner in = new Scanner(System.in);
		int selBtDevice;
		try {
			selBtDevice = in.nextInt();
		} catch (Exception e)
		{
			selBtDevice = -1;
		}
		// ...connect
		if (selBtDevice >= 0 && selBtDevice <= curBtDeviceIdx-1)
			fake_handleConnection("device"+(selBtDevice+1));
		else if(selBtDevice == 2 && curBtDeviceIdx ==2){
			fake_handleConnection("device1");
			fake_handleConnection("device2");
		}
		else
		{
			showMsg("No appropriate device selected. Exiting program.");
			System.exit(0);
		}
	}
	public void getBTDevice() {
		try {
			LocalDevice m_local = LocalDevice.getLocalDevice();
			DiscoveryAgent discoveryagent = m_local.getDiscoveryAgent();
			discoveryagent.startInquiry(DiscoveryAgent.GIAC,this);
			showMsg("Searching for Mindo-4S devices...");
		}
		catch (BluetoothStateException e) {
			System.out.println("BluetoothStateException:"+e.getMessage());

		}
	}
	public void inquiryCompleted(int discType) {
		showMsg("Search complete.");

		if (curBtDeviceIdx==0) {
			showMsg("No Mindo-4S devices found. Exiting program.");
			System.exit(1);
		}

		// print devices, allow user to select a device, and connect
		showMsg("--------------------------");
		showMsg("Select a device [0]-[" + (curBtDeviceIdx-1) + "]");
		for (int k=0;k<curBtDeviceIdx;k++) {
			showMsg("["+k + "] Mindo - 4S | MAC: " + btDevices[k].getBluetoothAddress());
		}
		showMsg("["+curBtDeviceIdx + "] : connect to Mindo4S+Mindo4H");
		System.out.print("> ");
		// ...device selection
		Scanner in = new Scanner(System.in);
		int selBtDevice;
		try {
			selBtDevice = in.nextInt();
		} catch (Exception e)
		{
			selBtDevice = -1;
		}
		// ...connect
		if (selBtDevice >= 0 && selBtDevice <= curBtDeviceIdx-1)
			startServiceSearch(btDevices[selBtDevice]);
		else if(selBtDevice == 2 && curBtDeviceIdx ==2){
			startServiceSearch(btDevices[0]);
			startServiceSearch(btDevices[1]);
		}
		else
		{
			showMsg("No appropriate device selected. Exiting program.");
			System.exit(0);
		}
	}
	public void serviceSearchCompleted(int transID, int respCode) {
		String msg = null;
		switch (respCode) {
		case SERVICE_SEARCH_COMPLETED:
			msg = "the service search completed normally";
			break;
		case SERVICE_SEARCH_TERMINATED:
			msg = "the service search request was cancelled by a call to DiscoveryAgent.cancelServiceSearch()";
			break;
		case SERVICE_SEARCH_ERROR:
			msg = "an error occurred while processing the request";
			break;
		case SERVICE_SEARCH_NO_RECORDS:
			msg = "no records were found during the service search";
			break;
		case SERVICE_SEARCH_DEVICE_NOT_REACHABLE:
			msg = "the device specified in the search request could not be reached or the local device could not establish a connection to the remote device";
			break;
		}
		showMsg("Service search completed - " + msg);
	}
	public void servicesDiscovered(int transId, ServiceRecord[] records) {
		showMsg("Service discovered.");
		for (int i = 0; i < records.length; i++) {
			//showMsg(records[i].toString());
			ServiceRecord rec = records[i];
			String url = rec.getConnectionURL(1,false);
			handleConnection(url);
		}
	}
	public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod){


		try {
			//showMsg("Found: " + btDevice.getFriendlyName(false));
			String devName = btDevice.getFriendlyName(false);
			if (devName.matches("Mindo - 4S") || devName.matches("Mindo - 4H")) {
				bluetoothAddress = btDevice.getBluetoothAddress();
				showMsg("Found "+btDevice.getFriendlyName(false) + "| MAC: " + bluetoothAddress);
				btDevices[curBtDeviceIdx++] = btDevice;
			}
			//else
			//showMsg("Found: "+btDevice.getFriendlyName(false));
		}
		catch (IOException ex1) {
			showMsg(ex1);
		}
	}
	private void startServiceSearch(RemoteDevice device) {

		try {
			showMsg("Search service in "+device.getFriendlyName(false));// + getDeviceStr(device));
			UUID uuids[] = new UUID[] { uuid };
			LocalDevice.getLocalDevice().getDiscoveryAgent().searchServices(null, uuids, device, this);
		} catch (Exception e) {
			showMsg(e);
		}
	}
	/*private String getDeviceStr(RemoteDevice btDevice) {
        return LocalDevice.getLocalDevice().getFriendlyName(btDevice) + " - 0x"
                        + btDevice.getBluetoothAddress();
}*/
	private void handleConnection(final String url) {
		System.out.println(url.substring(8, 19));
		if(url.substring(8, 19).equalsIgnoreCase("001AFF0900A")){
			Thread echo = new Thread() {
				public void run() {

					StreamConnection stream = null;

					/* create LSL stream */
					//System.load("liblslJNI32.jnilib");
					System.out.println("Library loaded!");

					showMsg("Trying to create LSL outlet");
					lsl = new liblsl();
					String streamName = url.substring(8, 19);
					//String streamName = "Mindo-4S-" + url.substring(url.length()-6);
					// declare a new streaminfo (name: streamName, content type: EEG, 4 channels, 128 Hz, float values, some made-up device id (can also be empty) */
					sinfo = new stream_info("Mindo1","EEG",numChans,samplingRate,channel_format_t.cf_float32); // 

					/* add some meta-data fields to it */
					xml_element desc, chn;
					desc = sinfo.desc();
					desc.append_child_value("manufacturer","Mindo");
					desc = desc.append_child("channels");
					for (int c=0;c<numChans;c++) {
						chn = desc.append_child("channel");
						chn.append_child_value("name",String.valueOf(c+1));
						chn.append_child_value("unit","microvolts");
						chn.append_child_value("type","EEG");
					}

					// make a new outlet (chunking: default, buffering: 360 seconds) 
					soutlet = new stream_outlet(sinfo, 0, 360);
					showMsg("Done! created LSL stream: " + streamName);

					try {

						/* open Bluetooth connection */
						stream = (StreamConnection) Connector.open(url);

						showMsg("Trying to open bluetooth stream");
						InputStream in = stream.openInputStream();
						OutputStream out = stream.openOutputStream();
						showMsg("Done, receiving data..");
						//date= Calendar.getInstance().getTime();
						//beginTime = date.getTime();
						//int a,b,c,d,tag=1;
						byte[]a = {(byte) 254};
						byte[]b = {0x05};//turn notch filter on
						byte[]c = {0x07};//0:24Bits, 7:SR=128, 8:SampleRate=256, 9:sr=512
						byte[]d = {0x12};//6:gain, 2: 4 channels
						byte[]e = {(byte)0xFF};//end
						out.write(a);
						out.write(b);
						out.write(c);
						out.write(d);
						out.write(e);

						byte[]aa = {(byte) 254};
						byte[]bb = {0x07};//start commend
						byte[]cc = {0x07};//0:24Bits, 7:SR=128, 8:SampleRate=256
						byte[]dd = {0x12};//1: gain=1, 6:gain=12, 2: 4 channels
						byte[]ee = {(byte)0xFF};//end
						out.write(aa);
						out.write(bb);
						out.write(cc);
						out.write(dd);
						out.write(ee);

						sample = new vectorf(numChans);
						showMsg("Receiving data on Mindo1 (numchans = " + numChans + ")");

						int counter=1;
						boolean mySynchronized = false;
						//System.out.print("\n"+url+": ");
						while(true){
							//now, I am trying to synchronize the data. Since the counter(header1) is from 240->255,
							//this while loop keep reading the data until matching the header1=240 and header2=114. Once
							//we found this pattern, we skip the following 14bytes (12bytes of data, 2bytes of junk).
							while(!mySynchronized && (in.read()==240 && in.read()==114)){
								mySynchronized=true;
								for(int i=0;i<14;i++)
									in.read();
							}
							//now, we start receiving data....
							if(mySynchronized && (in.read() == (240+counter))){
								if(in.read()==114){
									for (int i=0;i<numChans;i++){
										sample.set(i,(float)((in.read()<<24|in.read()<<16|in.read()<<8)/256));
										//System.out.println("OK: "+url);
									}
									in.read();
									in.read();
									counter++;
									if(counter==16)
										counter=0;
									soutlet.push_sample(sample);
									System.out.println("Stream1: "+sample.get(0));
								}
							}
						}
						//for testing
						/*while (true) {
						for (int k=0;k<numChans;k++) {
							sample.set(k, (float) 2.4*(in.read())/(2^23-1));
						}

						//volts = 2.4*receivedData/(2^23-1);

						soutlet.push_sample(sample,lsl_timeout);
						//System.out.println("Received: " + sample.toString());
					}*/
					} catch (IOException e) {
						showMsg(e.getMessage());
					} finally {
						showMsg("Closing LSL stream");
						soutlet.delete();
						showMsg("Close all sockets");
						showMsg("***************************");
						if (stream != null) {
							try {
								stream.close();
							} catch (IOException e) {
								showMsg(e);
							}
						}
					}
				}
			};
			echo.start(); 
		}
		else if(url.substring(8, 19).equalsIgnoreCase("001AFF09004")){
			Thread echo2 = new Thread() {
				public void run() {

					StreamConnection stream2 = null;

					/* create LSL stream2 */
					System.out.println("Loading LSL library...");
					System.out.println(System.getProperty("user.dir"));
					//System.load(System.getProperty("user.dir") + "/MacMindoReceiver_lib/liblslJNI32.jnilib");
					//System.load("liblslJNI32.jnilib");
					System.out.println("Library loaded!");

					showMsg("Trying to create LSL outlet");
					//lsl2 = new liblsl();
					String streamName = url.substring(8, 19);
					//String streamName = "Mindo-4S-" + url.substring(url.length()-6);
					// declare a new streaminfo (name: streamName, content type: EEG, 4 channels, 128 Hz, float values, some made-up device id (can also be empty) */
					sinfo2 = new stream_info("Mindo2","EEG",numChans,samplingRate,channel_format_t.cf_float32); // 

					/* add some meta-data fields to it */
					/*xml_element desc, chn;
					desc = sinfo2.desc();
					desc.append_child_value("manufacturer","Mindo");
					desc = desc.append_child("channels");
					for (int c=0;c<numChans;c++) {
						chn = desc.append_child("channel");
						chn.append_child_value("name",String.valueOf(c+1));
						chn.append_child_value("unit","microvolts");
						chn.append_child_value("type","EEG");
					}*/

					// make a new outlet (chunking: default, buffering: 360 seconds) 
					soutlet2 = new stream_outlet(sinfo2, 0, 360);
					showMsg("Done! created LSL stream2: " + streamName);

					try {

						/* open Bluetooth connection */
						stream2 = (StreamConnection) Connector.open(url);

						showMsg("Trying to open bluetooth stream2");
						InputStream in = stream2.openInputStream();
						OutputStream out = stream2.openOutputStream();
						showMsg("Done, receiving data..");
						//date= Calendar.getInstance().getTime();
						//beginTime = date.getTime();
						//int a,b,c,d,tag=1;
						byte[]a = {(byte) 254};
						byte[]b = {0x05};//turn notch filter on
						byte[]c = {0x07};//0:24Bits, 7:SR=128, 8:SampleRate=256, 9:sr=512
						byte[]d = {0x12};//6:gain, 2: 4 channels
						byte[]e = {(byte)0xFF};//end
						out.write(a);
						out.write(b);
						out.write(c);
						out.write(d);
						out.write(e);

						byte[]aa = {(byte) 254};
						byte[]bb = {0x07};//start commend
						byte[]cc = {0x07};//0:24Bits, 7:SR=128, 8:SampleRate=256
						byte[]dd = {0x12};//1: gain=1, 6:gain=12, 2: 4 channels
						byte[]ee = {(byte)0xFF};//end
						out.write(aa);
						out.write(bb);
						out.write(cc);
						out.write(dd);
						out.write(ee);

						sample2 = new vectorf(numChans);
						showMsg("Receiving data on Mindo2 (numchans = " + numChans + ")");

						int counter=1;
						boolean mySynchronized = false;
						//System.out.print("\n"+url+": ");
						while(true){
							//now, I am trying to synchronize the data. Since the counter(header1) is from 240->255,
							//this while loop keep reading the data until matching the header1=240 and header2=114. Once
							//we found this pattern, we skip the following 14bytes (12bytes of data, 2bytes of junk).
							while(!mySynchronized && (in.read()==240 && in.read()==114)){
								mySynchronized=true;
								for(int i=0;i<14;i++)
									in.read();
							}
							//now, we start receiving data....
							if(mySynchronized && (in.read() == (240+counter))){
								if(in.read()==114){
									for (int i=0;i<numChans;i++){
										sample2.set(i,(float)((in.read()<<24|in.read()<<16|in.read()<<8)/256));
										//System.out.println("OK: "+url);
									}
									in.read();
									in.read();
									counter++;
									if(counter==16)
										counter=0;
									soutlet2.push_sample(sample2);

									System.out.println("Stream2: "+sample2.get(0));
								}
							}
						}
						//for testing
						/*while (true) {
						for (int k=0;k<numChans;k++) {
							sample2.set(k, (float) 2.4*(in.read())/(2^23-1));
						}

						//volts = 2.4*receivedData/(2^23-1);

						soutlet.push_sample(sample2,lsl_timeout);
						//System.out.println("Received: " + sample2.toString());
					}*/
					} catch (IOException e) {
						showMsg(e.getMessage());
					} finally {
						showMsg("Closing LSL stream2");
						soutlet.delete();
						showMsg("Close all sockets2");
						showMsg("***************************");
						if (stream2 != null) {
							try {
								stream2.close();
							} catch (IOException e) {
								showMsg(e);
							}
						}
					}
				}
			};
			echo2.start(); 
		}
	}

	private void fake_handleConnection(String url) {
		if(url.equalsIgnoreCase("device1")){
			Thread echo = new Thread() {
				public void run() {

					StreamConnection stream = null;

					/* create LSL stream */
					//System.load("liblslJNI32.jnilib");
					System.out.println("Library loaded!");

					showMsg("Trying to create LSL outlet");
					lsl = new liblsl();
					String streamName = "";
					//String streamName = "Mindo-4S-" + url.substring(url.length()-6);
					// declare a new streaminfo (name: streamName, content type: EEG, 4 channels, 128 Hz, float values, some made-up device id (can also be empty) */
					sinfo = new stream_info("Mindo1","EEG",numChans,samplingRate,channel_format_t.cf_float32); // 

					/* add some meta-data fields to it */
					xml_element desc, chn;
					desc = sinfo.desc();
					desc.append_child_value("manufacturer","Mindo");
					desc = desc.append_child("channels");
					for (int c=0;c<numChans;c++) {
						chn = desc.append_child("channel");
						chn.append_child_value("name",String.valueOf(c+1));
						chn.append_child_value("unit","microvolts");
						chn.append_child_value("type","EEG");
					}

					// make a new outlet (chunking: default, buffering: 360 seconds) 
					soutlet = new stream_outlet(sinfo, 0, 360);
					showMsg("Done! created LSL stream: " + streamName);

					try {

						sample = new vectorf(numChans);
						showMsg("Receiving data on Mindo1 (numchans = " + numChans + ")");

						while(true){
							double data =0;
							for(int j=0;j<(samplingRate*60*60);j++){
								data = 0.5*Math.sin(j * 2 *  Math.PI * 9.25 / samplingRate)+0.5*Math.sin(j * 2 *  Math.PI * (Math.random()*10) / samplingRate);
								for (int i=0;i<numChans;i++){
									sample.set(i,(float)(data));
								}
								soutlet.push_sample(sample);
								try {
									sleep(1000/(int)samplingRate);
								} catch (InterruptedException e) {System.out.println("Excep: "+e.getMessage());}
							}
						}
						
					} catch (Exception e) {
						showMsg(e.getMessage());
					} finally {
						showMsg("Closing LSL stream");
						soutlet.delete();
						showMsg("Close all sockets");
						showMsg("***************************");
						if (stream != null) {
							try {
								stream.close();
							} catch (IOException e) {
								showMsg(e);
							}
						}
					}
				}
			};
			echo.start(); 
		}
		else if(url.equalsIgnoreCase("device2")){
			Thread echo2 = new Thread() {
				public void run() {

					StreamConnection stream2 = null;

					/* create LSL stream2 */
					System.out.println("Loading LSL library...");
					System.out.println(System.getProperty("user.dir"));
					//System.load(System.getProperty("user.dir") + "/MacMindoReceiver_lib/liblslJNI32.jnilib");
					//System.load("liblslJNI32.jnilib");
					System.out.println("Library loaded!");

					showMsg("Trying to create LSL outlet");
					//lsl2 = new liblsl();
					String streamName = "";
					//String streamName = "Mindo-4S-" + url.substring(url.length()-6);
					// declare a new streaminfo (name: streamName, content type: EEG, 4 channels, 128 Hz, float values, some made-up device id (can also be empty) */
					sinfo2 = new stream_info("Mindo2","EEG",numChans,samplingRate,channel_format_t.cf_float32); // 

					/* add some meta-data fields to it */
					/*xml_element desc, chn;
					desc = sinfo2.desc();
					desc.append_child_value("manufacturer","Mindo");
					desc = desc.append_child("channels");
					for (int c=0;c<numChans;c++) {
						chn = desc.append_child("channel");
						chn.append_child_value("name",String.valueOf(c+1));
						chn.append_child_value("unit","microvolts");
						chn.append_child_value("type","EEG");
					}*/

					// make a new outlet (chunking: default, buffering: 360 seconds) 
					soutlet2 = new stream_outlet(sinfo2, 0, 360);
					showMsg("Done! created LSL stream2: " + streamName);

					try {

						sample2 = new vectorf(numChans);
						showMsg("Receiving data on Mindo1 (numchans = " + numChans + ")");

						while(true){
							double data =0;
							for(int j=0;j<(samplingRate*60*60);j++){
								data = 0.5*Math.sin(j * 2 *  Math.PI * 9.25 / samplingRate)+0.5*Math.sin(j * 2 *  Math.PI * (Math.random()*10) / samplingRate);
								for (int i=0;i<numChans;i++){
									sample2.set(i,(float)(data));
								}
								soutlet2.push_sample(sample2);
								try {
									sleep(1000/(int)samplingRate);
								} catch (InterruptedException e) {System.out.println("Excep: "+e.getMessage());}
							}
						}
					} catch (Exception e) {
						showMsg(e.getMessage());
					} finally {
						showMsg("Closing LSL stream2");
						soutlet.delete();
						showMsg("Close all sockets2");
						showMsg("***************************");
						if (stream2 != null) {
							try {
								stream2.close();
							} catch (IOException e) {
								showMsg(e);
							}
						}
					}
				}
			};
			echo2.start(); 
		}
	}
	
	private void showMsg(Object msg){
		if(msg instanceof String)
			System.out.println(msg);
		else if(msg instanceof IOException){
			System.out.println("Exception: "+msg);
		}
	}
	private float[] singenerate() {//return a sin wave array
		float[] values = new float[600];
		float theta = 2.0f * (float) Math.PI * 1500/ 8000;
		for (int i = 0; i < 600; i++){
			values[i] = 1 * (float) Math.sin(i * theta);
			//System.out.println(values[i]);
		}
		return values;
	}
}
